package com.bd.udisk.controller;

import java.io.*;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;

import com.bd.udisk.entity.FileEntity;
import com.bd.udisk.util.Encrypt;
import com.bd.udisk.util.R;
import com.bd.udisk.vo.FileVo;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.apache.hadoop.fs.*;
import org.apache.hadoop.io.IOUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.*;

import com.bd.udisk.service.FileService;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;


/**
 * @author zsj
 * @email zsjemail666@163.com
 * @date 2023-12-08 18:55:08
 */
@RestController
@RequestMapping("/hdfs")
@Slf4j
public class FileController {
    @Autowired
    private FileService fileService;


    @Autowired
    @Lazy
    private FileSystem fileSystem;

    @ApiOperation(value = "获取指定目录下的所有信息")
    @GetMapping("/home")
    public R root(@NotNull @RequestHeader("login_user") String name,
                  @ApiParam("linux上hdfs的目录绝对路径") @RequestParam("r") String r) throws IOException {
        String target = '/' + name + r;
        if (!target.endsWith("/")) target += '/';//这里加这一行代码就可以了
        if (!checkIsExist(target)) return R.error("文件或者目录不存在");
        try {
            FileStatus[] fileStatuses = fileSystem.listStatus(new Path(target));
            List<FileVo> result = new ArrayList<>();
            //TODO FileUtil 是一个容易收到攻击的依赖  寻找替换
            Path[] paths = FileUtil.stat2Paths(fileStatuses);
            for (Path path : paths) {
                if (checkisDir(target, path)) {
                    result.add(new FileVo(path.getName(), true, target));
                } else result.add(new FileVo(path.getName(), false, target));
            }
            //做个排序
            List<FileVo> answer = redoListByResult(result);
            return R.ok().put("data", answer);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private List<FileVo> redoListByResult(List<FileVo> result) {
        List<FileVo> ans = new ArrayList<>();
        //先添加文件夹
        for (FileVo fileVo : result) {
            if (fileVo.isDir()) {
                ans.add(fileVo);
            }
        }
        result.removeAll(ans);
        //对文件夹进行排序
        ans = ans.stream().sorted((a, b) -> {
            return a.getLabel().charAt(0) - b.getLabel().charAt(0);
        }).collect(Collectors.toList());
        result.sort((v, c) -> {
            return v.getLabel().charAt(0) - c.getLabel().charAt(0);
        });
        ans.addAll(result);
        return ans;
    }


    @ApiOperation(value = "下载文件")
    @GetMapping("/get")
    public void getFile(@NotNull @RequestHeader("login_user") String name,
                        @ApiParam("linux上hdfs的文件的绝对路径") @RequestParam("path") String path,
                        HttpServletResponse response) throws IOException {
        log.debug("请求用户为{}", name);
        if (path.equals("/")) response.setHeader("message", "what fuck u want to do? u this motherFucker!");
        String prefix = "";
        String old = "";
        if (path.contains(".")) {
            old = path.substring(1, path.lastIndexOf("."));
            prefix = path.substring(path.lastIndexOf(".") + 1);
        } else {
            old = path.substring(1);
            prefix = "";
        }
        log.info("下载的文件目录为{}", path);
        if (checkIsExist(path)) {
            String filename = URLEncoder.encode(old, "UTF-8").replaceAll(" ", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + filename + '.' + prefix);
            ServletOutputStream outputStream = response.getOutputStream();//后面放到这里面
            FSDataInputStream open = fileSystem.open(new Path(path));
            IOUtils.copyBytes(open, outputStream, 2048, false);
        } else {
            response.setHeader("message", "File Is Not Exist!");
        }
    }


    /**
     * 这里无法直接使用文件对象进行上传 因为hdfs的上传方法只只支持路径的方式 <br/>
     * 但是java的 MultipartFile 对象并不能拿到文件的本地绝对路径<br/>
     */
    @ApiOperation("上传文件 支持多上传")
    @PostMapping("/upload")
    public R uploadFile(@NotNull @RequestHeader("login_user") String user,
                        @NotNull
                        @ApiParam(value = "文件地址 本地地址 记得使用转义字符", required = true)
                        @RequestBody List<String> local,
                        @NotNull
                        @ApiParam(value = "上传到hdfs上的路径名称 请使用‘/’隔开 像这样 /dir/target/filename/ 请一定要使用/收尾", required = true)
                        @RequestParam("path") String path,
                        @ApiParam(value = "若文件存在是否强制覆盖  0否(默认) 1是")
                        @RequestParam("rewrite") int rewrite) throws IOException {
        path = '/' + user + path;
        if (path.charAt(path.length() - 1) != '/') return R.error("路径非法");
        if (!checkIsExist(path)) return R.error("路径不存在");
        try {
            for (String file : local) {
                if (rewrite == 0) {
                    String real = file.substring(file.lastIndexOf('\\') + 1);
                    if (checkIsExist(path + real)) {
                        return R.error("文件已经存在了");
                    }
                }
                String finalPath = path;
                fileSystem.copyFromLocalFile(new Path(file), new Path(path));
                new Thread(() -> {
                    log.info("开始保存文件实体信息");
                    FileEntity fileEntity = new FileEntity();
                    fileEntity.setStatus(1);
                    fileEntity.setId(Encrypt.encrypt_uuid_12());
                    fileEntity.setFileName(file);
                    fileEntity.setBelong(user);
                    fileEntity.setCreateTime(new Date(System.currentTimeMillis()));
                    fileEntity.setFilePath(finalPath);
                    fileService.save(fileEntity);
                }, "saveFile").start();
            }
            return R.ok("上传成功");
        } catch (Exception e) {
            // does not exist
            return R.error(e.getMessage().
                    replace("File","文件").
                    replace("oes not exist","不存在"));
        }
    }

    @ApiOperation("创建文件夹")
    @GetMapping("/createDir")
    public R createDir(@RequestHeader("login_user") String name,
                       @ApiParam("文件夹绝对路径") @RequestParam("path") String path) throws IOException {
        path = '/' + name + '/' + path;
        boolean exist = checkIsExist(path);
        if (exist) return R.error("文件夹已经存在了");
        boolean mkdirs = fileSystem.mkdirs(new Path(path));
        return mkdirs ? R.ok("创建成功") : R.error("创建失败");
    }


    @ApiOperation("重命名或者移动文件夹")
    @GetMapping("/mv")
    public R renameOrMoveFile(@RequestHeader("login_user") String name,
                              @ApiParam("源路径") @RequestParam("src") String path,
                              @ApiParam("目标路径") @RequestParam("tar") String tar) throws IOException {
        path = '/' + name + path;
        tar = '/' + name + tar;
        boolean rOm = fileSystem.rename(new Path(path), new Path(tar));
        return rOm ? R.ok("操作成功") : R.error("操作失败");
    }


    @ApiOperation("删除文件或者文件夹")
    @DeleteMapping("/rm")
    public R rmFileOrDir(@RequestHeader("login_user") String name,
                         @ApiParam("目标对象 包括路径和是否为文件夹") @NotNull @RequestBody FileVo target) throws IOException {
        boolean result = false;
        //path是文件的路径
        //label是要操作的文件名称
        String path = target.getPath();
        String root = '/' + name + '/';//是谁的根目录
        if (Objects.isNull(path)) {
            return R.error("路径不能为空");
        }
        path += target.getLabel();
        if (!checkIsExist(path)) {
            return R.error("路径文件或文件夹不存在");
        }
        if (path.equals(root)) {
            return R.error("高危操作,不允许");
        }
        if (path.equals("/")) return R.error("绝对的越权操作");
        if (target.isDir()) {
            //说明是文件夹 此操作会删除文件夹下所有的文件
            try {
                result = fileSystem.delete(new Path(path), true);
            } catch (Exception e) {
                log.error(e.getMessage());
                return R.error(e.getMessage());
            }
        } else {
            result = fileSystem.delete(new Path(path), false);
        }
        return result ? R.ok("删除成功") : R.error("删除失败");
    }

    //查看文件内容 仅支持文本 todo

    //检查是否是一个文件夹
    private boolean checkisDir(String home, Path path) {
        try {
            return fileSystem.isDirectory(new Path(home + path.getName()));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    //检查文件或文件夹是否存在
    private boolean checkIsExist(String path) throws IOException {
        boolean exists = fileSystem.exists(new Path(path));
        if (exists) log.debug("{}文件或者目录是存在的", path);
        else log.debug("{}文件或者目录不存在", path);
        return exists;
    }
}
